package p2psvideo;

import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import p2pproxy.P2PSClient.DataItem;

@SuppressLint("NewApi")
class VideoTrackPlayer extends Thread implements IMediaTrackPlayer {

	private IAVPlayerCtl _ctl;
	private boolean _srcEof;
	private boolean _eof;
	private boolean _eofSent; 
	private volatile boolean _pause;
	private volatile boolean _quit;
	//是否已经渲染了第一帧
	private volatile boolean _firstFrameRendered;
	private boolean _headerSent;
	private Surface _sf;
	private MediaCodec _dec;
	private MediaFormat _format;
	private DataItem _header;
	private int _avlOutputBuf = -1;
	private List<DataItem> _frames = new LinkedList<DataItem>();
	private TimeQueue _vTimeQueue = new TimeQueue();
	private int _width;
	private int _height;
	private int _sarNum;
	private int _sarDen;
	private boolean _srcEmpty;
	private long _time=-1;
	private volatile boolean _inputDrain;	
	private BufferInfo _videoInfo = new BufferInfo();
	private ReentrantLock _lock = new ReentrantLock(); 
	
	public VideoTrackPlayer(IAVPlayerCtl ctl) {
		_ctl = ctl;		
		_srcEmpty = true;
	}
	
	private static boolean sameDataItem(DataItem it1, DataItem it2)
	{
		if (it1 == it2) return true;
		if (it1 == null) return false;
		if (it2 == null) return false;
		if (it1.type != it2.type) return false;
		if (it1.size != it2.size) return false;
		for(int i=0; i<it1.size; ++i) {
			if (it1.data[i] != it2.data[i]) return false;
		}
		return true;
	}
	
	private void releaseDec()
	{
		if (_dec != null) {
			try {
				_dec.stop();
			} catch(Throwable exp) {
				exp.printStackTrace();
			}
			try {
				_dec.release();
			} catch(Throwable exp) {
				exp.printStackTrace();
			}
			_dec = null;
		}
		_avlOutputBuf = -1;
		_headerSent = false;
		_vTimeQueue.reset();
		_inputDrain = false;
	}
	
	///发送eof到解码器
	private boolean sendEofToDec()
	{
		if (_dec == null) return true;
		int inputBufIdx = -1;
		try {
			inputBufIdx = _dec.dequeueInputBuffer(0);
		} catch(Throwable e) {
			e.printStackTrace();
			releaseDec();
			return true;
		}
		if (inputBufIdx != -1) {
			try {
				_dec.queueInputBuffer(inputBufIdx, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
				return true;
			} catch(Throwable e) {
				e.printStackTrace();
				releaseDec();
				return true;
			}
		}
		return false;
	}
	
	///检查结束
	private void checkEof()
	{
		if (_eof) return;
		if (!_srcEof) return;
		if (!_frames.isEmpty()) return;
		if (!_eofSent) return;
		if (_avlOutputBuf != -1) return;
		_eof = true;
	}
	
	///发送eof到编码器并渲染解码器的帧
	private boolean flushDec()
	{
		if (_dec == null) return true;
		sendEofToDec();
		dequeueFrame();
		if (_avlOutputBuf == -1) { //已经没有输出帧了
			releaseDec();
			return true;
		} else { //还有输出帧, 上游需要等下再次调用					
			return false;
		}
	}
	
	private boolean deliverFrame(DataItem it)
	{
		if (_dec == null) return false;
		int bufferIdx = -1;
		try {
			bufferIdx = _dec.dequeueInputBuffer(0);
		} catch(Throwable e) {
			e.printStackTrace();
			releaseDec();
			return false;
		}
		if (bufferIdx < 0) {
			_inputDrain = true;
			return false;
		} else {
			_inputDrain = false;
		}
		try {
			ByteBuffer buf;
			if (android.os.Build.VERSION.SDK_INT >= 21) {
				buf = _dec.getInputBuffer(bufferIdx);
			} else {
				buf = _dec.getInputBuffers()[bufferIdx];
			}
			buf.put(it.data, 0, it.size);	
			if (it.type == 4 || it.type == 5) {
				long pts = ((long)it.pts)*1000;
				_vTimeQueue.enqueue(pts);
				_dec.queueInputBuffer(bufferIdx, 0, it.size, pts, 0);
			} else if (it.type == 2 || it.type == 3) {
				_dec.queueInputBuffer(bufferIdx, 0, it.size, 0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
			}
			//Log.v("VideoTrackPlayer", "input");
		} catch(Throwable e) {			
			e.printStackTrace();
			releaseDec();
			return false;
		}
		return true;
	}
	
	private boolean dequeueFrame()
	{
		if (_dec == null) return false;
		if (_avlOutputBuf == -1) { //没有视频帧了
			int r = -1;			
			try {
				r = _dec.dequeueOutputBuffer(_videoInfo, 0);
				if (r == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
					MediaFormat fmt = _dec.getOutputFormat();
					_width = fmt.getInteger(MediaFormat.KEY_WIDTH);
					_height = fmt.getInteger(MediaFormat.KEY_HEIGHT);
					if (_sarNum != 0 && _sarDen != 0 && _width != 0) { //重新计算大小						
						int h = (_height * _sarDen) / _sarNum;
						if (h < _height) { //重新按高度算
							_width = _sarNum * _width / _sarDen;
						} else {
							_height = h;
						}
					}
					_ctl.notifyVideoInfo(_width, _height, _sarNum, _sarDen);
				}
			} catch(Throwable e) {
				e.printStackTrace();
				releaseDec();
			}
			_inputDrain = false;
			if (r >= 0) {				
				_avlOutputBuf = r;					
				//Log.v("VideoTrackPlayer", "decode");
				long tq;
				if (_videoInfo.presentationTimeUs == 0 || _videoInfo.presentationTimeUs == TimeQueue.INVALID_TIME) { //解码器的BUG，可能是错误的时间戳
					 tq = _vTimeQueue.dequeue();
					if (tq == TimeQueue.INVALID_TIME) { //无时间戳
						tq = _vTimeQueue.getLastOutputTime();
						if (tq == TimeQueue.INVALID_TIME) tq = 0;
					}
					_videoInfo.presentationTimeUs = tq;
				} else {
					tq = _vTimeQueue.dequeueFrom(_videoInfo.presentationTimeUs);					
				}
				//Log.v("VideoTrackPlayer", "Output video time: " + (_videoInfo.presentationTimeUs/1000) + ", tq=" + (tq/1000) + ", qsize: " + _vTimeQueue.size());			
			}
		}
		if (_avlOutputBuf == -1) return false;
		
		long t = _ctl.getSyncTime();
		boolean doRender  = false;
		long rt = -1;
		int v = android.os.Build.VERSION.SDK_INT;		
		/*
		if ( v >= 21) { //android 21版本以上考虑提前计算渲染时机
			if (t == -1 || t+100000 >= _videoInfo.presentationTimeUs || Math.abs(_videoInfo.presentationTimeUs - t) > 10000000L) {
				doRender = true;				
				long dt;
				if (t != -1) {
					dt = _videoInfo.presentationTimeUs - t;
					if (dt <= 0 || Math.abs(dt) > 10000000L) { //需要立即渲染 
						rt = -1;
						_time = _videoInfo.presentationTimeUs;
					} else if (dt > 500000)  { //尚未到渲染时机
						doRender = false;
					} else { //计算渲染时间
						rt = System.nanoTime() + dt*1000;
					}
				}
				if (t != -1) {
					_ctl.setSyncTime(t);
				} else {
					_ctl.setSyncTime(_videoInfo.presentationTimeUs);
				}
			}
		} else
		*/
		{ //21 版本以下，只有当时间到才渲染
			if (!_firstFrameRendered || t == -1 || t >= _videoInfo.presentationTimeUs || Math.abs(_videoInfo.presentationTimeUs - t) > 10000000L) {
				//Log.v("VideoTrackPlayer", "synct=" + t/1000 + ", vt=" + _videoInfo.presentationTimeUs/1000);
				doRender = true;									
				if (t != -1) {
					_ctl.setSyncTime(t);
				} else {
					_ctl.setSyncTime(_videoInfo.presentationTimeUs);
				}
				_firstFrameRendered = true;
				_time = _videoInfo.presentationTimeUs;
			}
		}						
		if (doRender) {				
			try {
				if (rt == -1) {
					_dec.releaseOutputBuffer(_avlOutputBuf, true);
				} else {
					_dec.releaseOutputBuffer(_avlOutputBuf, rt);
				}
				_inputDrain = false;
				_avlOutputBuf = -1;
				//Log.v("VideoTrackPlayer", "render");
				return true;
			} catch(Throwable e) { //surface 丢失?
				e.printStackTrace();
				releaseDec();
				return false;
			}				
		} else return false;		
	}
	
	private MediaCodec createProperCodec(MediaFormat mf) throws IOException
	{
		String mime = mf.getString(MediaFormat.KEY_MIME);		
		boolean newCodecs = android.os.Build.VERSION.SDK_INT >= 21;		
		int n;
		MediaCodecList m = null;
		MediaCodecInfo[] infos=null;
		if (newCodecs) {
			m = new MediaCodecList(MediaCodecList.ALL_CODECS);
			String name = m.findDecoderForFormat(mf);
			if (name != null) {
				return MediaCodec.createByCodecName(name);
			}
			infos = m.getCodecInfos();
			n = infos.length;
		} else {
			n = MediaCodecList.getCodecCount();
		}		
		for(int i=0; i<n; ++i) {
			MediaCodecInfo info;
			if (newCodecs) {
				info = infos[i];
			} else {
				info = MediaCodecList.getCodecInfoAt(i);
			}
			if (info.isEncoder()) continue;			
			String name=info.getName();
			if (name.equals("OMX.google.h264.decoder") || name.equals("OMX.google.hevc.decoder")) continue;
			String[] types = info.getSupportedTypes();
			if (types == null) continue;
			for(int j=0; j<types.length; ++j) {
				if (mime.equals(types[j])) {
					Log.v("VideoTrackPlayer", "Found codec: " + info.getName());
					return MediaCodec.createByCodecName(info.getName());
				}
			}
		}
		throw new IOException("No codec to handle this mime");
	}
	
	private boolean parseAVCSPSPPS()
	{
		if (_header == null || _format == null) return false;
		int naluStart=-1;
		int naluPos = -1;		
		int size = _header.size;
		ByteBuffer sps=null;
		ByteBuffer pps=null;
		
		for(int i=0; i< size; ++i) {			
			int left = size - i;
			if (_header.data[i] == 0) { //可能是开始码
				if (left >= 5) {
					int naluOffset = -1;
					if (_header.data[i+1] != 0) continue;
					if (_header.data[i+2] == 1) { // 00 00 01 格式
						naluOffset = 3;
					} else if (_header.data[i+2] != 0) continue;
					else if (_header.data[i+3] != 1) continue;
					else { //00 00 00 01 格式
						naluOffset = 4;
					}
					if (naluStart == -1) { //第一个nalu
						naluStart = i;
						naluPos = i + naluOffset;
					} else { //后一个nalu来临
						if ((_header.data[naluPos] & 0x1F) == 7) { //sps
							if (sps == null) sps = ByteBuffer.allocate(_header.size);
							sps.put(_header.data, naluStart, i - naluStart);							
						} else if ((_header.data[naluPos] & 0x1F) == 8) { //pps
							if (pps == null) {
								pps = ByteBuffer.allocate(_header.size);
							}
							pps.put(_header.data, naluStart, i - naluStart);
						}
						naluStart = i;
						naluPos = i + naluOffset;
					}
					i += naluOffset;
				}
			}
		}
		if (naluStart != -1) {
			if ((_header.data[naluPos] & 0x1F) == 7) { //sps
				if (sps == null) sps = ByteBuffer.allocate(_header.size);		
				sps.put(_header.data, naluStart, size - naluStart);
				_format.setByteBuffer("cs0", sps);
			} else if ((_header.data[naluPos] & 0x1F) == 8) { //pps
				if (pps == null) {
					pps = ByteBuffer.allocate(_header.size);
				}
				pps.put(_header.data, naluStart, size - naluStart);
			}
		}
		if (sps == null || pps == null) return false;
		
		_format.setByteBuffer("cs0", sps);
		_format.setByteBuffer("cs1", pps);
		return true;
	}
	private void _fillCS0CS1()
	{
		if (_format == null || _header == null) return;
		String mime = _format.getString(MediaFormat.KEY_MIME);		
		if (mime == null) return;
		if (mime.equals("video/avc")) {
			_headerSent = parseAVCSPSPPS();
		}
	}
	
	//尝试将帧推入解码器
	private boolean enqueueFrame()
	{
		if (_frames.isEmpty()) {
			_srcEmpty = true;
			if (_eof) return false;
			if (_srcEof && !_eofSent) { //尚未发生eof到解码器
				if (_dec != null) {					
					if (sendEofToDec()) {
						_eofSent = true;
					}
				}
				if (_dec == null) {
					_eofSent = true;
				}
			}
			if (_srcEof) {
				return dequeueFrame();	
			}
			return false;
		}
		DataItem it = _frames.get(0);
		
		switch(it.type) {
		case 0:  //重新打开解码器, 刷出解码器缓冲数据
			if (!flushDec()) {
				return false;
			}			
			String cfg = new String(it.data, 0, it.size);
			JSONObject mimeInfo;
			String mime="";
			int width=0;
			int height = 0;
			try {
				mimeInfo = new JSONObject(cfg);
				mime = mimeInfo.getString("mime");
				width = mimeInfo.getInt("width");
				height = mimeInfo.getInt("height");
				_sarNum = mimeInfo.optInt("sarNum",0);
				_sarDen = mimeInfo.optInt("sarDen", 0);
				Log.v("P2PSMediaPlayer", "video info, size=" + width + "x" + height + ", sar=" + _sarNum + "/" + _sarDen);
				if (width != 0 && height != 0) { //如果宽度不是16倍数，转换为16的倍数，不是16倍数导致在某些机型上出现斜屏
					if (width % 16 != 0) {
						width += 16 - width % 16;
					}
					/*if (height % 16 != 0) {
						height += 16 - height % 16;
					}*/
				}
			} catch (JSONException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();				
			}					
			if (width == 0 || height == 0) {
				width = 1280;
				height = 720;
			}
			if (mime.equals("video/h264")) {
				mime = "video/avc";
				try {					
					_format = new MediaFormat(); 
					_format.setString(MediaFormat.KEY_MIME, mime);
					_format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width*height);
					_format.setInteger(MediaFormat.KEY_WIDTH, width);
					_format.setInteger(MediaFormat.KEY_HEIGHT, height);					
				} catch(Throwable e) {
					e.printStackTrace();
					releaseDec();
				} 
			} else if (mime.equals("video/h265")) {
				mime = "video/hevc";
				try {
					_format = new MediaFormat(); 
					_format.setString(MediaFormat.KEY_MIME, mime);
					_format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width*height);
					_format.setInteger(MediaFormat.KEY_WIDTH, width);
					_format.setInteger(MediaFormat.KEY_HEIGHT, height);					
				} catch(Throwable e) {
					e.printStackTrace();
					releaseDec();
				} 
			}			
			_frames.remove(0);
			_ctl.freeFrame(it);
			return true;		
		case 2: //配置帧
			if (_format == null) { //没有格式帧
				_frames.remove(0);
				_ctl.freeFrame(it);
				return false;
			}
			boolean needRebuild = true;				
			if (sameDataItem(_header, it)) needRebuild = false;				
			if (needRebuild) { //需要重构
				if (!flushDec()) return false;
				if (_header != null) {
					_ctl.freeFrame(_header);						
				}
				_frames.remove(0);
				_header = it;
				//_fillCS0CS1();
				return true;
			} else { //无需重构
				_ctl.freeFrame(it);
				_frames.remove(0);
				return true;
			}		
		case 4: //普通帧
			if (_format == null) { //没有格式帧
				_frames.remove(0);
				_ctl.freeFrame(it);
				return false;
			}			
			if (_dec == null) { //创建解码器
				try {
					_dec = MediaCodec.createDecoderByType(_format.getString(MediaFormat.KEY_MIME));
					if (android.os.Build.VERSION.SDK_INT >= 18) {
						Log.v("P2PSMediaPlayer", "Using video decoder: " + _dec.getName());
					}
					_dec.configure(_format, _sf, null, 0);
					_dec.start();					
				} catch (Throwable e) {
					e.printStackTrace();
					releaseDec();
				}
			}
			if (_dec == null) { //解码器创建失败?				
				_ctl.freeFrame(it);
				_frames.remove(0);
				return false;
			}
			if (!_headerSent) { 
				if (_header == null) {
					_headerSent = true;
				} 
			}
			if (!_headerSent) {
				if (!deliverFrame(_header)) {
					return false;
				}
				_headerSent = true;
			}
			if (!deliverFrame(it)) {
				return false;
			}
			_ctl.freeFrame(it);
			_frames.remove(0);
			return true;					
		default:
			_ctl.freeFrame(it);
			_frames.remove(0);
			return true;
		}		
	}

	@Override
	public void run() {
		try {
			android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
		} catch(Exception e) {
			e.printStackTrace();
		}
		while(true) {
			while (_pause && !_quit && _firstFrameRendered) {
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (_quit) break;
			boolean r=false;
			_lock.lock();		
			if (!_inputDrain) {
				r = enqueueFrame();
			}
			r = r || dequeueFrame();
			checkEof();			
			_lock.unlock();
			if (_srcEmpty || !r) {
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		super.run();
	}

	@Override
	public int getBufferLength() {
		_lock.lock();		
		try {
			if (_frames.size() < 1) return 0;
			 int delta = _frames.get(_frames.size()-1).dts - _frames.get(0).dts;
			 if (delta < 0) return 0;
			 else return delta;
		} finally {
			_lock.unlock();
		}
	}

	@Override
	public void setSurface(Surface sf) {		
		_lock.lock();
		try {		
			if (_sf == sf) return;
			_sf = sf;
			releaseDec();
		} finally {
			_lock.unlock();
		}
	}

	@Override
	public void pause(boolean pauseOrResume) {
		_pause = pauseOrResume;
	}
	
	@Override
	public void pushFrame(DataItem it) {		
		_lock.lock();
		try {
			if (_quit || _eof) {
				if (it != null) {
					_ctl.freeFrame(it);
				}
				return;
			}
			if (_srcEof && it != null) {
				_ctl.freeFrame(it);
				return;
			}
			if (it == null) {
				_srcEof = true;
				return;
			}
			_srcEmpty = false;
			_frames.add(it);
		} finally {
			_lock.unlock();
		}
	}

	@Override
	public boolean eof() {		
		return _eof;
	}

	@Override
	public void start() {
		super.start();
	}

	@Override
	public void close() {		
		_lock.lock();
		try {
			if (_quit) return;
			_quit = true;
		} finally {
			_lock.unlock();
		}
		this.interrupt();
		try {
			join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
		_lock.lock();		
		try {
			if (_header != null) {
				_ctl.freeFrame(_header);
				_header = null;
			}
			while(!_frames.isEmpty()) {
				_ctl.freeFrame(_frames.get(0));
				_frames.remove(0);
			}
			releaseDec();
			_ctl = null;
		} finally {
			_lock.unlock();
		}		
	}
	
	public int getWidth()
	{
		return _width;
	}
	
	public int getHeight()
	{
		return _height;
	}

	@Override
	public void notifySyncTimeChanged() {
		if (_inputDrain && _avlOutputBuf == -1) return;
		if (!_lock.tryLock()) return;		
		try {
			if (_quit) return;
			boolean r = false;
			for(int i=0; i<10; ++i) {
				r = false;
				if (!_inputDrain) r = enqueueFrame();
				r = r || dequeueFrame();
				checkEof();
				if (!r) break;
			}
		} finally {
			_lock.unlock();
		} 
	}

	@Override
	public long getTime() {
		return _time;
	}

}
